Expand description
Introduction
This crate helps you read and write GeoJSON — a format for encoding geographic data structures.
To get started, add geojson
to your Cargo.toml
.
cargo add geojson
Types and crate structure
This crate is structured around the GeoJSON spec (IETF RFC 7946),
and users are encouraged to familiarise themselves with it. The elements specified in this spec
have corresponding struct and type definitions in this crate, e.g. FeatureCollection
, Feature
,
etc.
There are two primary ways to use this crate.
The first, most general, approach is to write your code to deal in terms of these structs from the GeoJSON spec. This allows you to access the full expressive power of GeoJSON with the speed and safety of Rust.
Alternatively, and commonly, if you only need geometry and properties (and not, e.g.
foreign members), you can bring your own
types, and use this crate’s serde
integration to serialize and deserialize your custom
types directly to and from a GeoJSON Feature Collection. See more on using your own types with
serde.
If you want to use GeoJSON as input to or output from a geometry processing crate like
geo
, see the section on using geojson with
geo-types.
Using structs from the GeoJSON spec
A GeoJSON object can be one of three top-level objects, reflected in this crate as the
GeoJson
enum members of the same name.
- A
Geometry
represents points, curves, and surfaces in coordinate space. - A
Feature
usually contains aGeometry
and some associated data, for example a “name” field or any other properties you’d like associated with theGeometry
. - A
FeatureCollection
is a list of one or moreFeature
s.
Because Feature
and FeatureCollection
are more flexible, bare Geometry
GeoJSON
documents are rarely encountered in the wild. As such, conversions from Geometry
or Geometry Value
to Feature
objects are provided via the From
trait.
Beware: A common point of confusion arises when converting a GeoJson
GeometryCollection
. Do you want it converted to a single
Feature
whose geometry is a GeometryCollection
, or do you
want a FeatureCollection
with each element of the
GeometryCollection
converted to its own Feature
, potentially
with their own individual properties. Either is possible, but it’s important you understand
which one you want.
Examples
Reading
GeoJson
can be deserialized by calling str::parse
:
use geojson::{Feature, GeoJson, Geometry, Value};
use std::convert::TryFrom;
let geojson_str = r#"
{
"type": "Feature",
"properties": { "food": "donuts" },
"geometry": {
"type": "Point",
"coordinates": [ -118.2836, 34.0956 ]
}
}
"#;
let geojson: GeoJson = geojson_str.parse::<GeoJson>().unwrap();
let feature: Feature = Feature::try_from(geojson).unwrap();
// read property data
assert_eq!("donuts", feature.property("food").unwrap());
// read geometry data
let geometry: Geometry = feature.geometry.unwrap();
if let Value::Point(coords) = geometry.value {
assert_eq!(coords, vec![-118.2836, 34.0956]);
}
Writing
GeoJson
can be serialized by calling to_string
:
use geojson::{Feature, GeoJson, Geometry, Value};
let geometry = Geometry::new(Value::Point(vec![-120.66029, 35.2812]));
let geojson = GeoJson::Feature(Feature {
bbox: None,
geometry: Some(geometry),
id: None,
// See the next section about Feature properties
properties: Some(get_properties()),
foreign_members: None,
});
let geojson_string = geojson.to_string();
Feature properties
The geojson
crate is built on top of serde_json
. Consequently,
some fields like feature.properties
hold serde_json
values.
use geojson::{JsonObject, JsonValue};
let mut properties = JsonObject::new();
let key = "name".to_string();
properties.insert(key, JsonValue::from("Firestone Grill"));
Parsing
GeoJSON’s spec is quite simple, but it has several subtleties that must be taken into account when parsing it:
- The
geometry
field of aFeature
is anOption
— it can be blank. GeometryCollection
s contain otherGeometry
objects, and can nest.- We strive to produce strictly valid output, but we are more permissive about what we accept as input.
Here’s a minimal example which will parse and process a GeoJSON string.
use geojson::{GeoJson, Geometry, Value};
/// Process top-level GeoJSON Object
fn process_geojson(gj: &GeoJson) {
match *gj {
GeoJson::FeatureCollection(ref ctn) => {
for feature in &ctn.features {
if let Some(ref geom) = feature.geometry {
match_geometry(geom)
}
}
}
GeoJson::Feature(ref feature) => {
if let Some(ref geom) = feature.geometry {
match_geometry(geom)
}
}
GeoJson::Geometry(ref geometry) => match_geometry(geometry),
}
}
/// Process GeoJSON geometries
fn match_geometry(geom: &Geometry) {
match geom.value {
Value::Polygon(_) => println!("Matched a Polygon"),
Value::MultiPolygon(_) => println!("Matched a MultiPolygon"),
Value::GeometryCollection(ref gc) => {
println!("Matched a GeometryCollection");
// !!! GeometryCollections contain other Geometry types, and can
// nest — we deal with this by recursively processing each geometry
for geometry in gc {
match_geometry(geometry)
}
}
// Point, LineString, and their Multi– counterparts
_ => println!("Matched some other geometry"),
}
}
fn main() {
let geojson_str = r#"
{
"type": "GeometryCollection",
"geometries": [
{"type": "Point", "coordinates": [0,1]},
{"type": "MultiPoint", "coordinates": [[-1,0],[1,0]]},
{"type": "LineString", "coordinates": [[-1,-1],[1,-1]]},
{"type": "MultiLineString", "coordinates": [
[[-2,-2],[2,-2]],
[[-3,-3],[3,-3]]
]},
{"type": "Polygon", "coordinates": [
[[-5,-5],[5,-5],[0,5],[-5,-5]],
[[-4,-4],[4,-4],[0,4],[-4,-4]]
]},
{ "type": "MultiPolygon", "coordinates": [[
[[-7,-7],[7,-7],[0,7],[-7,-7]],
[[-6,-6],[6,-6],[0,6],[-6,-6]]
],[
[[-9,-9],[9,-9],[0,9],[-9,-9]],
[[-8,-8],[8,-8],[0,8],[-8,-8]]]
]},
{"type": "GeometryCollection", "geometries": [
{"type": "Polygon", "coordinates": [
[[-5.5,-5.5],[5,-5],[0,5],[-5,-5]],
[[-4,-4],[4,-4],[0,4],[-4.5,-4.5]]
]}
]}
]
}
"#;
let geojson = geojson_str.parse::<GeoJson>().unwrap();
process_geojson(&geojson);
}
Use geojson with other crates by converting to geo-types
geo-types
are a common geometry format used across many
geospatial processing crates. The geo-types
feature is enabled by default.
From geo-types to geojson
From
is implemented on the Value
enum variants to allow conversion from geo-types
Geometries.
// requires enabling the `geo-types` feature
let geo_point: geo_types::Point<f64> = geo_types::Point::new(2., 9.);
let geo_geometry: geo_types::Geometry<f64> = geo_types::Geometry::from(geo_point);
assert_eq!(
geojson::Value::from(&geo_point),
geojson::Value::Point(vec![2., 9.]),
);
assert_eq!(
geojson::Value::from(&geo_geometry),
geojson::Value::Point(vec![2., 9.]),
);
If you wish to produce a FeatureCollection
from a homogenous collection of geo-types
, a
From
impl is provided for geo_types::GeometryCollection
:
// requires enabling the `geo-types` feature
use geojson::FeatureCollection;
use geo_types::{polygon, point, Geometry, GeometryCollection};
use std::iter::FromIterator;
let poly: Geometry<f64> = polygon![
(x: -111., y: 45.),
(x: -111., y: 41.),
(x: -104., y: 41.),
(x: -104., y: 45.),
].into();
let point: Geometry<f64> = point!(x: 1.0, y: 2.0).into();
let geometry_collection = GeometryCollection::from_iter(vec![poly, point]);
let feature_collection = FeatureCollection::from(&geometry_collection);
assert_eq!(2, feature_collection.features.len());
From geojson to geo-types
The optional geo-types
feature implements the TryFrom
trait, providing fallible conversions to geo-types Geometries
from GeoJSON Value
enums.
In most cases it is assumed that you want to convert GeoJSON into geo
primitive types in
order to process, transform, or measure them:
match
ongeojson
, iterating over itsfeatures
field, yieldingOption<Feature>
.- process each
Feature
, accessing itsValue
field, yieldingOption<Value>
.
Each Value
represents a primitive type, such as a coordinate, point,
linestring, polygon, or its multi- equivalent, and each of these has an equivalent geo
primitive type, which you can convert to using the std::convert::TryFrom
trait.
GeoJSON to geo_types::GeometryCollection
Unifying these features, the quick_collection
function accepts a GeoJson
enum
and processes it, producing a GeometryCollection
whose members can be transformed, measured, rotated, etc using the algorithms and functions in
the geo
crate:
// requires enabling the `geo-types` feature
use geo_types::GeometryCollection;
use geojson::{quick_collection, GeoJson};
let geojson_str = r#"
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
-0.13583511114120483,
51.5218870403801
]
}
}
]
}
"#;
let geojson = geojson_str.parse::<GeoJson>().unwrap();
// Turn the GeoJSON string into a geo_types GeometryCollection
let mut collection: GeometryCollection<f64> = quick_collection(&geojson).unwrap();
Convert GeoJson
to geo_types::Geometry<f64>
// requires enabling the `geo-types` feature
use geo_types::Geometry;
use geojson::GeoJson;
use std::convert::TryInto;
use std::str::FromStr;
let geojson_str = r#"
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
-0.13583511114120483,
51.5218870403801
]
}
}
"#;
let geojson = GeoJson::from_str(geojson_str).unwrap();
// Turn the GeoJSON string into a geo_types Geometry
let geom: geo_types::Geometry<f64> = geojson.try_into().unwrap();
Caveats
- Round-tripping with intermediate processing using the
geo
types may not produce identical output, as e.g. outerPolygon
rings are automatically closed. geojson
attempts to output valid geometries. In particular, it may re-orientPolygon
rings when serialising.
The geojson_example
and
polylabel_cmd
crates contain example
implementations which may be useful if you wish to perform this kind of processing yourself and require
more granular control over performance and / or memory allocation.
Using your own types with serde
If your use case is simple enough, you can read and write GeoJSON directly to and from your own types using serde.
Specifically, the requirements are:
- Your type has a
geometry
field.- If your
geometry
field is ageo-types
Geometry, you must use the providedserialize_with
/deserialize_with
helpers. - Otherwise, your
geometry
field must be acrate::Geometry
.
- If your
- Other than
geometry
, you may only use a Feature’sproperties
- all other fields, like foreign members, will be lost.
#[derive(serde::Serialize, serde::Deserialize)]
struct MyStruct {
// Serialize as geojson, rather than using the type's default serialization
#[serde(serialize_with = "serialize_geometry", deserialize_with = "deserialize_geometry")]
geometry: geo_types::Point<f64>,
name: String,
count: u64,
}
See more in the serialization and deserialization modules.
Re-exports
Modules
- Build your struct from GeoJSON using
serde
- Module for all GeoJSON-related errors
- Write your struct to GeoJSON using
serde
Structs
- Feature Objects
- Feature Collection Objects
- Enumerates individual Features from a GeoJSON FeatureCollection
- Write Features to a FeatureCollection
- Geometry Objects
Enums
- GeoJSON Objects
- The underlying value for a
Geometry
.
Functions
- quick_collection
geo-types
A shortcut for producinggeo_types
GeometryCollection objects from arbitrary valid GeoJSON input.
Type Definitions
- Bounding Boxes
- Positions